﻿#region Disclaimer/Info
///////////////////////////////////////////////////////////////////////////////////////////////////
// MetaCollection
// 
// MetaCollection is a system to provide a standard structure, permissions and metadata service
// for content organization.
//
// This project is hosted at Google Code at http://code.google.com/p/metacollection/
//
// This project is licensed under the BSD license.  See the License.txt file for more information.
///////////////////////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Store;
using MetaCollection.Framework.Model;
using MetaCollection.Framework.Security;

namespace MetaCollection.Framework.Index
{
	/// <summary>
	/// This class maintains an index for a segment of a repository using item metadata.  Only the
	/// item metadata information is indexed.  Resources are not indexed by this Repository Index.
	/// 
	/// The intention behind the design is to allow the indexing of an arbitrary sized tree of
	/// information.  An implementation constraint on the index writer is that there can be only
	/// one (MacLeod pattern). With this in mind we will have a queue of work that feeds the writer. We
	/// are going to all the trouble to have a work queue because the ItemRepositoryIndex will also
	/// be subscribing to item change notifications from the list of request handlers.
	/// </summary>
	/// <remarks>
	/// Implementation Detail -
	/// 
	///		- State machine based workflow.  UpdateStatus() will change the operation modes of the
	///		class.  Do not set the m_Status directly or a deadlock may result.  Do not block based on
	///		the CurrentStatus or deadlocks may result.
	///		- Work Queue
	///		- Single Thread of Index Writing
	///		- Periodic refreshing of Index Reader (TODO)
	///		- Checking for existing writer lock and shutting down index operations. In cases where 
	///		there are multiple processes it is possible that this instance will not own or not be
	///		able to obtain a write lock.  When this happens the system should suspend indexing operations.
	///		It may be appropriate to retry to index based on period of refresh.
	///		
	///		- Configuration
	///			- MAXQUEUE - max write queue length (TODO??)
	///			- DELAY - handler request delay time (throttles load on itemrepositories)
	///			- COLLECTIONROOT - index uri segment (col://root/node) for indexing item and children
	///			- INDEXVERSIONS - index all item versions (all or published, default to published)
	///			- LOCATION - Directory where the index files will be/are stored
	///			- NAME - name of the index.  Used to identify the ItemRepositoryIndex within
	///			  the collection of indexes in the RequestHandlerFactory
	///			  
	///			- TIMEOUT - the duration of time to wait when attempting to enqueue work.
	///			  The default is infinite.
	///			  
	///			- CREATE - if true then the index is created.  Any existing index is deleted
	///			
	/// </remarks>
	public class ItemRepositoryIndex : IRepositoryIndex
	{
		#region member variables
		MetaPrincipal m_SystemUser = MetaPrincipal.SystemPrincipal;
		/// <summary>
		/// An object to lock on for shared state that is not thread safe already.
		/// </summary>
		object m_SharedLock = new object();
		/// <summary>
		/// Id of this repository index type
		/// </summary>
		readonly Guid m_Id = new Guid("1455F367-7D9F-4277-AAC8-BDFBF3BC5F2E");
		/// <summary>
		/// Name of this Item Index (can be overridden through configuraion).
		/// </summary>
		string m_Name = "Item Repository Index";
		/// <summary>
		/// Indicates if this class has been disposed.
		/// </summary>
		bool m_Disposed;
		/// <summary>
		/// Allows specification of the request handler used to process requests.  This is used
		/// for testing purposes to allow dependency injection and get around the static singleton
		/// that is the RequestHandlerFactory.
		/// </summary>
		IRequestHandler m_RequestHandler;
		/// <summary>
		/// Directory where the Index files are stored.
		/// </summary>
		FSDirectory m_Store;
		/// <summary>
		/// File System path to the directory where files are stored. <see cref="m_Store"/>
		/// </summary>
		string m_Location;
		/// <summary>
		/// Indicates if the index location should be replaced/rebuilt with a new copy.
		/// </summary>
		bool m_Create = true;
		/// <summary>
		/// The URI of the items within the collection to index.  This allows multiple segment indexes
		/// for better query/update performance.
		/// </summary>
		Uri m_CollectionUri;
		/// <summary>
		/// Delay time (in milliseconds) between requests for more items from the index.
		/// </summary>
		private int m_RequestDelay = 100;
		/// <summary>
		/// A timeout value that can be used to error out index uri enqueue operations when the queue
		/// is full.
		/// </summary>
		int m_Timeout = TimeSpan.FromMinutes(5).Milliseconds;
		/// <summary>
		/// if set to <c>true</c> then all versions of an item are indexed and not just the published version.
		/// </summary>
		bool m_IndexVersions;
		/// <summary>
		/// Status of this index.
		/// </summary>
		RepositoryIndexStatus m_Status;
		/// <summary>
		/// Cancelation token associated with the index status.  Used to signal methods that status is changing and operations should stop.
		/// </summary>
		CancellationTokenSource m_Cancel = new CancellationTokenSource();
		/// <summary>
		/// The indexing queue
		/// </summary>
		BlockingCollection<Uri> m_IndexList = new BlockingCollection<Uri>();
		/// <summary>
		/// The queue of documents to write to the index.
		/// </summary>
		BlockingCollection<Document> m_WriteQueue = new BlockingCollection<Document>();
		#endregion

		#region constructors
		/// <summary>
		/// Initializes a new instance of the item repository index. Note that
		/// indexing does not begin until the Initialize method is called with
		/// configuration data on what to index and where to store it.
		/// </summary>
		public ItemRepositoryIndex()
		{
			m_Disposed = false;
			m_Status = RepositoryIndexStatus.Initial;
		}

		/// <summary>
		/// Initializes a new instance of the item repository index using the 
		/// provided request handler to service requests to index the repository.
		/// </summary>
		/// <param name="requestHandler">the handler to use as the source of
		/// items to index</param>
		public ItemRepositoryIndex(IRequestHandler requestHandler)
		{
			m_Disposed = false;
			m_Status = RepositoryIndexStatus.Initial;
			m_RequestHandler = requestHandler;
		}
		#endregion

		/// <summary>
		/// Splits a connection string apart to get configuration data.  Sets the found
		/// values.
		/// </summary>
		/// <exception cref="ArgumentException">thrown if the connection string contains invalid values.</exception>
		/// <param name="connString"></param>
		void ParseConnectionString(string connString)
		{
			Func<string, string> PathResolve = (System.Web.HttpContext.Current != null ? (Func<string, string>)System.Web.HttpContext.Current.Server.MapPath : Path.GetFullPath);
			if (string.IsNullOrWhiteSpace(connString))
				return;
			string[] configElements = connString.TrimEnd(';').Split(';');
			foreach (var element in configElements) {
				string[] elementParts = element.Split('=');
				if (elementParts.Length != 2)
					throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "Invalid Configuration Setting: '{0}'", element));
				switch(elementParts[0].ToUpperInvariant().Trim()) {
					case "LOCATION": m_Location = PathResolve(elementParts[1]); break;
					case "NAME": m_Name = elementParts[1]; break;
					//case "MAXQUEUE" : break; // max queue length for writing documents
					case "REFRESH" : break; // max age between index crawls, 0 to disable.
					case "DELAY": m_RequestDelay = int.Parse(elementParts[1], CultureInfo.CurrentCulture); break;
					case "CREATE": m_Create = Boolean.Parse(elementParts[1]); break;
					case "TIMEOUT": m_Timeout = int.Parse(elementParts[1], CultureInfo.CurrentCulture); break;
					case "INDEXVERSIONS": m_IndexVersions = bool.Parse(elementParts[1]); break;
					case "COLLECTIONROOT": m_CollectionUri = new Uri(elementParts[1]); break;
					default : break; // we could throw an exception here but for future proofing and adding extra elements this is a bad idea.
				}
			}
		}

		/// <summary>
		/// Acquires a lock and updates the repository state.
		/// </summary>
		/// <remarks>
		/// The following is a list of approved state transitions.
		/// 
		///		From		| To
		///		-------------------------------------------------------------
		///		Building	| Disposed, Ready, Cancel, Suspend
		///		Initial		| Ready
		///		Ready		| Building [via BeginIndexing]
		///		Disposed	| (none)
		///		Cancel		| Ready [via m_Cancel.Cancel()]
		///		Suspend		| Ready [via m_IndexList.CompleteAdding();]
		///		-------------------------------------------------------------
		/// </remarks>
		/// <param name="newStatus">the status we want to transition to</param>
		internal void UpdateStatus(RepositoryIndexStatus newStatus)
		{
			if (m_Status != newStatus)
			{
				lock (m_SharedLock) // we can't have updates being made while we are computing if an update is valid and making the change
				{
					bool invalid = false;
					// from initial we can go to disposed or ready states
					invalid |= ((m_Status == RepositoryIndexStatus.Initial) && (newStatus != RepositoryIndexStatus.Ready && newStatus != RepositoryIndexStatus.Disposed));
					// if we are in disposed state then nothing else is valid.
					invalid |= (m_Status == RepositoryIndexStatus.Disposed);
					// from cancel or suspended we can go to ready or disposed.
					invalid |= ((m_Status == RepositoryIndexStatus.Suspended ||
						m_Status == RepositoryIndexStatus.Cancel) && (newStatus != RepositoryIndexStatus.Ready && newStatus != RepositoryIndexStatus.Disposed));
					// from Ready we can go to cancel (does nothing), Building or Disposed.
					invalid |= (m_Status == RepositoryIndexStatus.Ready && (newStatus != RepositoryIndexStatus.Building && newStatus != RepositoryIndexStatus.Disposed && newStatus != RepositoryIndexStatus.Cancel));

					if (invalid)
						throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, "Current Status {0} does not support transitioning to {1}", m_Status, newStatus));

					// at this point everything is good for the update.
					m_Status = newStatus;
				}

				if (newStatus == RepositoryIndexStatus.Cancel && m_Cancel != null && !m_Cancel.IsCancellationRequested)
					m_Cancel.Cancel(); // when tasks finish state sets to Ready.
				else if (newStatus == RepositoryIndexStatus.Building)
					BeginIndexing();
				else if (newStatus == RepositoryIndexStatus.Suspended)
				{
					if (m_IndexList != null && !m_IndexList.IsAddingCompleted)
						m_IndexList.CompleteAdding(); // when tasks finish state sets to Ready.
				}
			}
		}

		#region indexing operations and methods

		static System.Security.Principal.IPrincipal GetIndexPrincipal()
		{
			// add a claim that we are the current system to the current process thread identity.
			return new MetaPrincipal(Thread.CurrentPrincipal.Identity, new List<IItemClaim> { new ItemClaim { Id = Guid.Empty, Rights = AccessRights.Unknown, ClaimType = MetaPrincipal.NameClaimType, ClaimValue = "SYSTEM" } });
		}

		/// <summary>
		/// Performs initialization based on the current settings of configuration values.
		/// </summary>
		/// <remarks>should be called only by the UpdateStatus method as the result of a valid state transition.</remarks>
		void BeginIndexing()
		{
			ThreadPool.QueueUserWorkItem(delegate
			{
				Thread.Sleep(5000); // wait till after initialization has finished before starting these tasks.
				
				// clean house, cleanup states
				if (m_IndexList == null || m_IndexList.IsCompleted)
					m_IndexList = new BlockingCollection<Uri>();
				if (m_WriteQueue == null || m_WriteQueue.IsCompleted)
					m_WriteQueue = new BlockingCollection<Document>();
				if (m_Cancel == null || m_Cancel.IsCancellationRequested)
					m_Cancel = new CancellationTokenSource();
				
				var tasks = new List<Task>();
				tasks.Add(Task.Factory.StartNew((s) => { CrawlRepository(); }, null, TaskCreationOptions.LongRunning));
				tasks.Add(Task.Factory.StartNew((s) => { ProcessIndexList(); }, null, TaskCreationOptions.LongRunning));
				tasks.Add(Task.Factory.StartNew((s) => { WriteDocumentIndex(); }, null, TaskCreationOptions.LongRunning));
			//	Task.Factory.ContinueWhenAll(tasks.ToArray(), (t) => { UpdateStatus(RepositoryIndexStatus.Ready); });
				Task.Factory.ContinueWhenAll(tasks.ToArray(), (t) => 
				{
					if (t.Any(s => s.Exception != null))
						UpdateStatus(RepositoryIndexStatus.Cancel);  // ensure the cancel token was set.
					UpdateStatus(RepositoryIndexStatus.Ready); 
				});
			});
		}

		/// <summary>
		/// Feeds URIs to scan into the index scan list.
		/// This is the first stage of the pipeline, producing work.
		/// </summary>
		/// <remarks>This method should only be called by BeginIndexing as part of a Task</remarks>
		void CrawlRepository()
		{
			Thread.CurrentThread.Name = "IndexCrawler";
			Thread.CurrentPrincipal = GetIndexPrincipal();

			if (!m_Create)
				return;
			else
				m_Create = false; // make sure we only run through this once.
			
			UpdateStatus(RepositoryIndexStatus.Building); 

			using (var logWriter = new StreamWriter(Path.Combine(m_Location, "index-list.log"), true, Encoding.ASCII))
			using (IRequestHandler handler = RequestHandlerFactory.CreateRequestHandler())
			{
				logWriter.WriteLine(String.Format(CultureInfo.InvariantCulture, "Begining New Index of Repository - {0}", DateTime.Now));
				try
				{
					var items = handler.ItemRequest(m_SystemUser, RequestTypes.GetItem | RequestTypes.ProcessAllDescendents, m_CollectionUri, LoadOptions.Href);
					foreach (var item in items)
					{
						logWriter.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0}\t\t{1}\t\t{2}", DateTime.Now, m_IndexList.Count, item.Href));
						if (item.Href != m_CollectionUri && !m_IndexList.IsAddingCompleted)
							m_IndexList.TryAdd(item.Href);
					}
				}
				catch (Exception ex)
				{
					logWriter.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0}\t{1}", DateTime.Now, "Unhandled Exception while Indexing Resouce -"));
					logWriter.Write(ex.ToString());
					logWriter.Flush();
					logWriter.Close();
					throw; // this is going to hurt...
				}
				logWriter.WriteLine("Finished Crawling Subfolder.");
			}
		}

		/// <summary>
		/// This stage of the pipeline takes URIs from the list of items to index 
		/// and adds documents for the item to the write stage of the pipeline.
		/// </summary>
		/// <remarks>This task will run and block until canceled.  This should be
		/// run from a long running task.
		/// This method should only be called by BeginIndexing as part of a Task</remarks>
		void ProcessIndexList()
		{
			Thread.CurrentThread.Name = "IndexParser";
			Thread.CurrentPrincipal = GetIndexPrincipal();

			using (var logWriter = new StreamWriter(Path.Combine(m_Location, "index-process.log"), true, Encoding.ASCII))
			using (IRequestHandler handler = RequestHandlerFactory.CreateRequestHandler())
			{
				logWriter.WriteLine(String.Format(CultureInfo.InvariantCulture, "Begining Processing Of Index - {0}", DateTime.Now));
				IDocumentFactory factory = new ItemSpecificationDocumentFactory(handler);
				Uri uri;
				while (m_IndexList.TryTake(out uri, m_Timeout, m_Cancel.Token))
				{
					try
					{
						var docs = factory.CreateDocument(uri, m_IndexVersions);
						int versionCount = 0;
						foreach (var doc in docs) {
							m_WriteQueue.Add(doc);
							versionCount++;
						}
						logWriter.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0}\t\t{1}\t{3}\t{2}\t", DateTime.Now, m_IndexList.Count, uri, versionCount));
					}
					catch (SecurityException) // if the current user (SYSTEM) does not have access then head onto the next
					{
						logWriter.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0}\t{1}{2}", DateTime.Now, "Access Denied to resource -", uri));
						continue;
					}
					catch (Exception ex)
					{
						logWriter.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0}\t{1}{2}", DateTime.Now, "Unhandled Exception while Indexing Resouce -", uri));
						logWriter.Write(ex.ToString());
						throw; // this is going to kill our indexing...
					}
					Thread.Sleep(m_RequestDelay); // TODO: timer signaling would be a better implementation since any attempts to cancel are not heard while we sleep
				}
				logWriter.WriteLine(String.Format(CultureInfo.InvariantCulture, "Finished Processing Index - {0}", DateTime.Now));
				m_WriteQueue.CompleteAdding();
			}
		}

		/// <summary>
		/// This method writes pending documents to the index on disk.
		/// </summary>
		/// <remarks>This method will run and write each document in the queue, blocking
		/// until work is available until canceled.
		/// This method should only be called by BeginIndexing as part of a Task</remarks>
		void WriteDocumentIndex()
		{
			Thread.CurrentThread.Name = "IndexWriter";
			using (var logWriter = new StreamWriter(Path.Combine(m_Location, "index-info.log"), true, Encoding.ASCII))
			{
				logWriter.WriteLine(String.Format(CultureInfo.InvariantCulture, "Begining Write of Repository Index - {0}", DateTime.Now));
				try
				{
					var writer = new IndexWriter(m_Store, new Lucene.Net.Analysis.Standard.StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), new IndexWriter.MaxFieldLength(25000)); //, m_Create
					writer.SetInfoStream(logWriter);
					foreach (var doc in m_WriteQueue.GetConsumingEnumerable(m_Cancel.Token))
					{
						writer.AddDocument(doc);
					}
					writer.Optimize();
					writer.Close();
				}
				catch (Exception ex)
				{
					logWriter.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0}\t{1}", DateTime.Now, "Unhandled Exception while Writing the Index"));
					logWriter.Write(ex.ToString());
					logWriter.Flush();
					logWriter.Close();
					throw; // this is going to kill our indexing...
				}
				logWriter.WriteLine(String.Format(CultureInfo.InvariantCulture, "Finished Creating Index - {0}", DateTime.Now));
			}
			UpdateStatus(RepositoryIndexStatus.Ready);
		}

		/// <summary>
		/// Attempts to enqueue an item uri to scan
		/// </summary>
		/// <param name="itemUri"></param>
		/// <param name="errorOnFail"></param>
		internal void TryEnqueueWork(Uri itemUri, bool errorOnFail = false)
		{
			if (itemUri == null)
				throw new ArgumentNullException("itemUri", "Uri of item to index can not be null.");
			if (CurrentStatus == RepositoryIndexStatus.Initial && errorOnFail)
				throw new InvalidOperationException("Repository must be initialized before use.");
			if (CurrentStatus == RepositoryIndexStatus.Ready)
				UpdateStatus(RepositoryIndexStatus.Building);
			// if we are in the transition state this uri will be lost and that sucks
			// TODO: fix this so we somehow capture the uri to index while in the transition of restarting the index.
			if (!m_IndexList.IsAddingCompleted)
			{
				if (!m_IndexList.TryAdd(itemUri, m_Timeout) && errorOnFail)
					throw new InvalidOperationException("Could not enqueue work.");
			}
		}
		#endregion

		#region IRepositoryIndex Members

		/// <summary>
		/// Opens a new Index Reader on the Index Store.
		/// </summary>
		/// <returns></returns>
		[CLSCompliant(false)]
		public IndexReader CreateReader()
		{
			return IndexReader.Open(m_Store, true);
		}

		/// <summary>
		/// Suspends the crawling of the repository to build the index.  Notifications
		/// of changes to items from the provider sources are still queued for processing
		/// once <see cref="ResumeIndexing"/> is called.
		/// </summary>
		public void SuspendIndexing()
		{
			UpdateStatus(RepositoryIndexStatus.Suspended);
		}

		/// <summary>
		/// Resumes normal indexing operations previously suspended by <see cref="SuspendIndexing"/>.
		/// If the index is not currently suspended no action is taken.
		/// </summary>
		public void ResumeIndexing()
		{
			UpdateStatus(RepositoryIndexStatus.Building);
		}

		/// <summary>
		/// The current status of indexing operations.
		/// </summary>
		public RepositoryIndexStatus CurrentStatus { get { return m_Status; } }

		/// <summary>
		/// Gets the length of the items pending index.
		/// </summary>
		public int IndexQueueLength { get { return m_IndexList.Count(); } }


		#endregion

		#region IRepository Members

		/// <summary>
		/// Initializes the repository using the configuration string data.
		/// </summary>
		/// <param name="configData">a connection string containing parameters to use for initalizing the index</param>
		public void Initialize(string configData)
		{
			if (CurrentStatus != RepositoryIndexStatus.Initial)
				throw new InvalidOperationException("Can not initialize Item Repository Index when repository is not in the initial state.");
			if (string.IsNullOrWhiteSpace(configData))
				throw new ArgumentNullException("configData", "A proper connection string is required.");
			ParseConnectionString(configData);
			var d = new DirectoryInfo(m_Location);
			if (m_Create)
			{
				if (d.Exists)
					System.IO.Directory.Delete(d.FullName, true);// d.Delete(true);
				d.Create();
			}
			
			m_Store = FSDirectory.Open(d.CreateSubdirectory("index")); // stores the index in a sub folder
			UpdateStatus(RepositoryIndexStatus.Ready);

			if (m_Create)
			{
				if (m_CollectionUri == null)
					throw new InvalidOperationException("Can not create an index when the Collection Root is not set in the connection string.");
				TryEnqueueWork(m_CollectionUri, true);
				UpdateStatus(RepositoryIndexStatus.Building);
			}
		}

		#endregion

		#region IProvider Members

		/// <summary>
		/// Unique Identifier of this provider.
		/// </summary>
		public Guid Id
		{
			get { return m_Id; }
		}

		/// <summary>
		/// Gets the name of this provider.
		/// </summary>
		/// <value>The name.</value>
		public string Name
		{
			get { return m_Name; }
		}

		/// <summary>
		/// Gets the friendly description of this provider.
		/// </summary>
		/// <value>The description.</value>
		public string Description
		{
			get { return "A Repository Search Index that is built off of the metadata stored in an Item record.  Configuration of elements is controlled by the ItemSpecifications."; }
		}

		/// <summary>
		/// Initializes the provider specified in the ProviderType.
		/// </summary>
		/// <param name="providerConfig">The provider config.</param>
		/// <exception cref="System.ArgumentNullException">The name of the provider is null or one of the Repositories is null and it can not be created internally.</exception>
		/// <exception cref="System.ArgumentException">The name of the provider has a length of zero</exception>
		/// <exception cref="System.InvalidOperationException"> An attempt is made to call
		/// MetaCollection.Framework.Provider.IProvider.Initialize(MetaCollection.Framework.Model.IProviderType)
		/// on a provider after the provider has already been initialized</exception>
		public void Initialize(Configuration.ProviderReferenceElement providerConfig)
		{
			if (providerConfig == null)
				throw new ArgumentNullException("providerConfig", "Provider Configuration can not be null");
			
			Initialize(providerConfig.Text);

			if (!providerConfig.Name.Equals(m_Name, StringComparison.Ordinal))
				throw new ArgumentException("Provider Configuration Name mismatch.  Provider Configuration name and connection string name given do not match");
		}

		/// <summary>
		/// Gets the Request Events that are supported by this provider.
		/// </summary>
		/// <value>RequestEventTypes supported by this provider</value>
		public RequestEventTypes SupportedEvents
		{
			get { return RequestEventTypes.OnProvideItem; }
		}

		#endregion

		#region IRequestSink Members

		/// <summary>
		/// Hook for monitoring requests that create changes to an item.  When
		/// changes are detected the item(s) changed will be added to the index
		/// work queue for processing.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		public void OnProvideItem(object sender, EventArgs args)
		{
			ItemContext context = sender as ItemContext;
			if (context == null)
				throw new InvalidOperationException("The Current Request Context of this request can not be null.");

			if (context.IsValidRequest.GetValueOrDefault(false) && context.RequestType.HasType(RequestTypes.SetItem))
			{
				// perform a check to ensure that the changed item is under our collection heirarchy we are indexing.
				if (context.RequestPath.AbsolutePath.StartsWith(m_CollectionUri.AbsolutePath, StringComparison.OrdinalIgnoreCase)) 
					TryEnqueueWork(context.RequestPath);
			}
		}

		#region not implemented sink events

		public void OnRequestStart(object sender, EventArgs args)
		{
			throw new NotImplementedException();
		}

		public void OnBeforeValidateRequest(object sender, EventArgs args)
		{
			throw new NotImplementedException();
		}

		public void OnRequestValidateComplete(object sender, EventArgs args)
		{
			throw new NotImplementedException();
		}

		public void OnBeforePathResolve(object sender, EventArgs args)
		{
			throw new NotImplementedException();
		}

		public void OnPathResolveComplete(object sender, EventArgs args)
		{
			throw new NotImplementedException();
		}

		public void OnBeforeInvokeRequest(object sender, EventArgs args)
		{
			throw new NotImplementedException();
		}

		public void OnValidateRequest(object sender, EventArgs args)
		{
			throw new NotImplementedException();
		}

		public void OnPathResolve(object sender, EventArgs args)
		{
			throw new NotImplementedException();
		}

		public void OnProvideResource(object sender, EventArgs args)
		{
			throw new NotImplementedException();
		}

		public void OnHandlerDisposing(object sender, EventArgs args)
		{
			throw new NotImplementedException();
		}

		public void OnRequestComplete(object sender, EventArgs args)
		{
			throw new NotImplementedException();
		}

		#endregion

		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			Dispose(true);
			// This object will be cleaned up by the Dispose method.
			// Therefore, you should call GC.SupressFinalize to
			// take this object off the finalization queue
			// and prevent finalization code for this object
			// from executing a second time.
			GC.SuppressFinalize(this);
		}

		// Dispose(bool disposing) executes in two distinct scenarios.
		// If disposing equals true, the method has been called directly
		// or indirectly by a user's code. Managed and unmanaged resources
		// can be disposed.
		// If disposing equals false, the method has been called by the
		// runtime from inside the finalizer and you should not reference
		// other objects. Only unmanaged resources can be disposed.
		private void Dispose(bool disposing)
		{
			// Check to see if Dispose has already been called.
			if (!m_Disposed)
			{
				if (disposing) // dispose managed resources
				{
					UpdateStatus(RepositoryIndexStatus.Cancel); // give cleanup a chance
					if (m_Store != null)
						m_Store.Close();
					if (m_RequestHandler != null)
						m_RequestHandler.Dispose();
					if (m_WriteQueue != null)
						m_WriteQueue.Dispose();
					if (m_IndexList != null)
						m_IndexList.Dispose();
					if (m_Cancel != null)
						m_Cancel.Dispose();
				}

				// Call the appropriate methods to clean up unmanaged resources here.

				// Note that disposing has been done.
				UpdateStatus(RepositoryIndexStatus.Disposed);
				m_Disposed = true;
			}
		}


		#endregion
	}
}
